const { log, ensure, equals } = require('./utils')

const TokenType = Object.freeze({
    // 数字
    number: 'number',
    // +
    plus: 'plus',
    // -
    subtract: 'subtract',
    // * 
    multiply: 'multiply',
    // /
    divide: 'divide',
    // (
    parenthesesLeft: 'parenthesesLeft',
    // )
    parenthesesRight: 'parenthesesRight',

})

const isDigit = c => {
    let digits = '0123456789'
    return digits.includes(c)
}
const isAuto = c => {
    let autos = '+-*/()'
    return autos.includes(c)
}
const isSpace = function(c) {
    const spaces = ' \n\t'
    return spaces.includes(c)
}

const isMulOrDiv = op => '*/'.includes(op)

const isPlusOrSub = op => '+-'.includes(op)

class Token {
    constructor(value, type) {
        this.setup(value, type)
    }
    setup(value, type) {
        this.value = value
        this.type = type
        this.autoType()
    }
    autoType() {
        const types = {
            '+': TokenType.plus,
            '-': TokenType.subtract,
            '*': TokenType.multiply,
            '/': TokenType.divide,
            '(': TokenType.parenthesesLeft,
            ')': TokenType.parenthesesRight,
        }
        if (this.type === TokenType.auto) {
            this.type = types[this.value]
        }
    }
}

class TokenList {
    constructor(tokens) {
        this.tokens = tokens
        this.index = 0
    }
    readToken() {
        let i = this.index
        this.index += 1
        if (i < this.tokens.length) {
            return this.tokens[i]
        } else {
            return null
        }
    }
    peekToken(offset = 0) {
        let i = this.index + offset
        if (i < this.tokens.length) {
            return this.tokens[i]
        } else {
            return null
        }
    }
    hasToken() {
        return this.index < this.tokens.length
    }
}

class Code {
    constructor(content) {
        this.content = content
        this.index = 0
    }

    // 判断是否读到 content 的末尾
    hasChar() {
        return this.index < this.content.length
    }
    // 读取当前下标的字符, 且下标 +1
    readChar() {
        if (this.hasChar()) {
            let i = this.index
            this.index += 1
            return this.content[i]
        } else {
            return null
        }
    }
    // 获取当前下标的字符，下标不变
    peekChar() {
        if (this.hasChar()) {
            return this.content[this.index]
        } else {
            return null
        }
    }
    readNumber() {
        let result = ''
        while (this.hasChar()) {
            let c = this.peekChar()
            if (isDigit(c)) {
                result += this.readChar()
            } else {
                break
            }
        }
        return Number(result)
    }
}

const tokenizer = content => {
    const code = new Code(content)
    let tokens = []
    while (code.hasChar()) {
        let c = code.peekChar()
        if (isDigit(c)) {
            let n = code.readNumber()
            let t = new Token(n, TokenType.number)
            tokens.push(t)
        } else if (isAuto(c)) {
            let v = code.readChar()
            let t = new Token(v, TokenType.auto)
            tokens.push(t)
        } else if (isSpace(c)) {
            code.readChar()
        } else {
            throw new Error(`wrong token ${c}`)
        }
    }
    return tokens
}

const parseParentheses = tokens => {
    let ts = tokens
    tokens.readToken()
    let ast = parseExpression(ts)
    let t = ts.readToken()
    // log('t ', t.type)
    // if (t.type !== TokenType.parenthesesRight) {
    //     log(`ast error token ${t.value}`)
    // }
    return ast
}
const parseAtom = tokens => {
    let ts = tokens
    let t = ts.peekToken()
    if (t.type === TokenType.number) {
        return ts.readToken().value
    } else if (t.type === TokenType.parenthesesLeft) {
        return parseParentheses(ts)
    } else {
        log(`parseAtom token ${t.value}`)
    }
}

const parseMultiply = tokens => {
    let ts = tokens
    let ast = null
    let left = parseAtom(tokens)
    ast = left

    while (ts.hasToken() && isMulOrDiv(ts.peekToken().value)) {
        let op = ts.readToken().value
        let right = parseAtom(ts)
        ast = [op, ast, right]
    }
    return ast
}
const parsePlus = tokens => {
    let ts = tokens
    let ast = null
    let left = parseMultiply(tokens)
    ast = left

    while (ts.hasToken() && isPlusOrSub(ts.peekToken().value)) {
        let op = ts.readToken().value
        let right = parseMultiply(ts)
        ast = [op, ast, right]
    }
    return ast
}

const parseExpression = tokens => {
    let ts = tokens
    let ast = parsePlus(ts)
    return ast
}
const test1 = () => {
    let s1 = `
        1 + 2
    `
    let expect = [
        '+', 1, 2
    ]
    let ts = tokenizer(s1)
    let tl = new TokenList(ts)
    let output = parseExpression(tl)
    ensure(equals(expect, output), 'test1')
}

const test2 = () => {
    let s1 = `
        1 + 2 + 3
    `
    let expect = [
        '+', ['+', 1, 2], 3
    ]
    let ts = tokenizer(s1)
    let tl = new TokenList(ts)
    let output = parseExpression(tl)
    ensure(equals(expect, output), 'test2')
}

const test3 = () => {
    let s1 = `
        1 + 2 * 3
    `
    let expect = [
        '+', 1, ['*', 2, 3] 
    ]
    let ts = tokenizer(s1)
    let tl = new TokenList(ts)
    let output = parseExpression(tl)
    ensure(equals(expect, output), 'test3')
}

const test4 = () => {
    let s1 = `
        1 + 2 * 3 - 4
    `
    let expect = [
        '-', ['+', 1, ['*', 2, 3] ], 4
    ]
    let ts = tokenizer(s1)
    let tl = new TokenList(ts)
    let output = parseExpression(tl)
    ensure(equals(expect, output), 'test4')
}
const test5 = () => {
    let s1 = `
        (1 + 2) * (3 - 4)
    `
    let expect = [
        '*', ['+', 1, 2 ], ['-', 3, 4] 
    ]
    let ts = tokenizer(s1)
    let tl = new TokenList(ts)
    let output = parseExpression(tl)
    ensure(equals(expect, output), 'test5')
}


const test6 = () => {
    let s1 = `
        ((1 + 2) * 3) - 4 / 5
    `
    let expect = [
        '-', ['*', ['+', 1, 2 ], 3], ['/', 4, 5] 
    ]
    let ts = tokenizer(s1)
    let tl = new TokenList(ts)
    let output = parseExpression(tl)
    ensure(equals(expect, output), 'test6')
}
const __main = () => {
    test1()
    test2()
    test3()
    test4()
    test5()
    test6()
}

__main()